package logHandle;

/**
 * 
 * @author limeng
 *
 */
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Map.Entry;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.AppenderLoggingException;
import org.apache.logging.log4j.core.appender.SocketAppender;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.layout.PatternLayout;

import logHandle.exception.LogServiceException;
import logHandle.exception.LogServiceExceptionEnum;

/**

 *
 */
/**
 * 
 * @since JDK 1.8
 * @version
 * @author 李萌
 * @Email limeng32@chinaunicom.cn
 * @date 2017年11月6日 下午2:42:31 ;
 * @ClassName LogService ;
 *
 * @Description * 使用此服务需要设定环境变量LOG4J2_XML_PATH <br/>
 *              该变量为指向包含log4j2.xml和logstash.properties文件的文件夹<br/>
 *              logstash.properties内容格式为: <br/>
 *              target=IP1:port1,IP2:port2-port3 <br/>
 *              pattern="custom layout" <可选>
 * @Reason ADDREASON;
 * 
 *
 */
public class LogService {

	private static class InnerInstance {
		private static final LogService instance = new LogService();
	}

	private org.apache.logging.log4j.core.Logger logger = null;

	private static final int threadStepForLogServiceCalled = 4;

	private static final int threadStepForLogServiceCalledOnSwitch = 5;

	private LogService() {
		initLogger();
	}

	/**
	 * 从环境变量LOG4J2_XML_PATH中指定的路径+/log4j2.xml组成的全路径查找Logger需要的配置并返回一个ConfigurationSource对象 <br/>
	 * 如果LOG4J2_XML_PATH没有指定内容，则从./log4j2.xml中加载配置
	 * 
	 * @return
	 */
	private ConfigurationSource fetchSource() {
		// 从环境变量中获取存放log4j2.xml的文件夹路径
		String path = GetEnv.getLog4J2Path();
		String standbyPath = ".";
		ConfigurationSource source = null;
		if (path == null || "".equals(path.trim())) {
			// 如果该路径不存在，则使用备用设置，即从tomcat根目录处获取log4j2.xml文件
			path = standbyPath;
		}
		// 如果该路径存在，则找到这个log4j2.xml文件，并加载到ConfigurationSource中。

		// 先尝试使用 path 生成source
		String xmlPath = FetchConfig.getXmlPath(path);
		try (FileInputStream fileInputStream = new FileInputStream(xmlPath);) {
			source = new ConfigurationSource(fileInputStream);
		} catch (IOException e) {
			String logMsg = "使用path=" + xmlPath + " 生成source时异常:"
					+ e.getMessage();
			System.out.println(logMsg);
			// 再尝试使用 备用配置 生成source
			xmlPath = FetchConfig.getXmlPath(standbyPath);
			try (FileInputStream fileInputStream = new FileInputStream(
					xmlPath);) {
				source = new ConfigurationSource(fileInputStream);
			} catch (IOException e2) {
				logMsg = "使用备用path=" + xmlPath + " 生成source时异常:"
						+ e2.getMessage();
				System.out.println(logMsg);
			}
		}
		return source;
	}

	/**
	 * 从环境变量LOG4J2_XML_PATH中指定的路径+/logstash.properties组成的全路径查找可用的Logstash IP:port并加载到loggercontext中 <br/>
	 * 如果LOG4J2_XML_PATH没有指定内容，则从./logstash.properties中加载配置
	 * 
	 * @throws LogServiceException
	 */
	synchronized private void initLogger() {
		if (logger != null) {
			return;
		}

		// 初始化LoggerContext
		LogManager.shutdown();
		ConfigurationSource configurationSource = fetchSource();
		if (configurationSource == null) {
			try {
				throw new LogServiceException(
						LogServiceExceptionEnum.CouldntFindAvailableLogstashConfiguration
								.toString());
			} catch (Exception e) {
				System.out.println("InitLogger出错:" + e.getMessage());
			}
			return;
		}
		Configurator.initialize(null, configurationSource);

		// 声明一个PatternLayout供Appender使用
		PatternLayout patternLayout = PatternLayout.newBuilder()
				.withPattern(ServiceWarden.getPattern()).build();
		// 获取一个可用的Logstash服务器IP和端口
		Entry<String, Integer> hostAndPort = ServiceWarden.getCompetentOne();
		if (hostAndPort == null) {
			try {
				throw new LogServiceException(
						LogServiceExceptionEnum.CouldntFindAvailableLogstashConfiguration
								.toString());
			} catch (Exception e) {
				System.out.println("InitLogger出错:" + e.getMessage());
			}
			return;

		}
		// 声明一个SocketAppender
		SocketAppender.Builder<?> socketAppenderBuilder = SocketAppender
				.newBuilder().withName(FetchConfig.SOCKET_NAME)
				.withHost(hostAndPort.getKey()).withPort(hostAndPort.getValue())
				.withLayout(patternLayout).withIgnoreExceptions(false);
		SocketAppender socketAppender = socketAppenderBuilder.build();
		socketAppender.start();
		// 将其加入到当前的LoggerContext的RootLogger中
		logger = (Logger) LogManager.getLogger(this.getClass());
		logger.setLevel(Level.ALL);
		logger.setAdditive(false);
		logger.addAppender(socketAppender);
	}

	/**
	 * 如果isSecondTimeCalled为false（即第一次尝试打印），将当前线程栈中第<code>threadStepForLogServiceCalled</code>个线程信息加到log中， 因为这个线程是调用LogService打印时的位置
	 * 如果withThread方法发生改变，<code>threadStepForLogServiceCalled</code>的值可能也会改变
	 * 如果isSecondTimeCalled为true（即第二次尝试打印），将当前线程栈中第<code>threadStepForLogServiceCalledOnSwitch</code>个线程信息加到log中， 因为这个线程是调用LogService打印时的位置
	 * 如果withThread方法发生改变，<code>threadStepForLogServiceCalledOnSwitch</code>的值可能也会改变
	 * 
	 * @param log
	 */
	private StringBuffer withThread(String log, boolean isSecondTimeCalled,
			boolean isDirect) {
		StackTraceElement[] elements = Thread.currentThread().getStackTrace();
		StringBuffer ret = new StringBuffer();
		StringBuffer cookedStringBuffer = null;
		if (isSecondTimeCalled) {
			// for (StackTraceElement e : elements) {
			// ret.append(e).append(" ");
			// }
			int stepIndex = threadStepForLogServiceCalledOnSwitch;
			if (!isDirect) {
				stepIndex++;
			}
			cookedStringBuffer = CookThreadMessage.cook(elements[stepIndex]);
		} else {
			int stepIndex = threadStepForLogServiceCalled;
			if (!isDirect) {
				stepIndex++;
			}
			cookedStringBuffer = CookThreadMessage.cook(elements[stepIndex]);
		}
		ret.append(cookedStringBuffer).append(" ").append(log);
		return ret;
	}

	/**
	 * 如果isSecondTimeCalled为false，表示是首次调用，再次尝试以便发现新的配置文件。 如果isSecondTimeCalled为true，表示是第二次调用，不再尝试。
	 * 
	 * @param log
	 * @param isSecondTimeCalled
	 */
	private void doTrace(String log, boolean isSecondTimeCalled,
			boolean isDirectCall) {
		if (logger == null) {
			initLogger();
			if (!isSecondTimeCalled) {
				doTrace(log, true, isDirectCall);
			}
		} else {
			try {
				logger.trace(withThread(log, isSecondTimeCalled, isDirectCall));
			} catch (AppenderLoggingException e) {
				logger = null;
				initLogger();
				if (!isSecondTimeCalled) {
					doTrace(log, true, isDirectCall);
				}
			}
		}
	}

	/**
	 * 如果isSecondTimeCalled为false，表示是首次调用，再次尝试以便发现新的配置文件。 如果isSecondTimeCalled为true，表示是第二次调用，不再尝试。
	 * 
	 * @param log
	 * @param isSecondTimeCalled
	 */
	private void doDebug(String log, boolean isSecondTimeCalled,
			boolean isDirectCall) {
		if (logger == null) {
			initLogger();
			if (!isSecondTimeCalled) {
				doDebug(log, true, isDirectCall);
			}

		} else {
			try {
				logger.debug(withThread(log, isSecondTimeCalled, isDirectCall));
			} catch (AppenderLoggingException e) {
				logger = null;
				initLogger();
				if (!isSecondTimeCalled) {
					doDebug(log, true, isDirectCall);
				}
			}
		}
	}

	/**
	 * 如果isSecondTimeCalled为false，表示是首次调用，再次尝试以便发现新的配置文件。 如果isSecondTimeCalled为true，表示是第二次调用，不再尝试。
	 * 
	 * @param log
	 * @param isSecondTimeCalled
	 */
	private void doInfo(String log, boolean isSecondTimeCalled,
			boolean isDirectCall) {
		if (logger == null) {
			initLogger();
			if (!isSecondTimeCalled) {
				doInfo(log, true, isDirectCall);
			}

		} else {
			try {
				logger.info(withThread(log, isSecondTimeCalled, isDirectCall));
			} catch (AppenderLoggingException e) {
				logger = null;
				initLogger();
				if (!isSecondTimeCalled) {
					doInfo(log, true, isDirectCall);
				}
			}
		}
	}

	/**
	 * 如果isSecondTimeCalled为false，表示是首次调用，再次尝试以便发现新的配置文件。 如果isSecondTimeCalled为true，表示是第二次调用，不再尝试。
	 * 
	 * @param log
	 * @param isSecondTimeCalled
	 */
	private void doWarn(String log, boolean isSecondTimeCalled,
			boolean isDirectCall) {
		if (logger == null) {
			initLogger();
			if (!isSecondTimeCalled) {
				doWarn(log, true, isDirectCall);
			}
		} else {
			try {
				logger.warn(withThread(log, isSecondTimeCalled, isDirectCall));
			} catch (AppenderLoggingException e) {
				logger = null;
				initLogger();
				if (!isSecondTimeCalled) {
					doWarn(log, true, isDirectCall);
				}
			}
		}
	}

	/**
	 * 如果isSecondTimeCalled为false，表示是首次调用，再次尝试以便发现新的配置文件。 如果isSecondTimeCalled为true，表示是第二次调用，不再尝试。
	 * 
	 * @param log
	 * @param isSecondTimeCalled
	 */
	private void doError(String log, boolean isSecondTimeCalled,
			boolean isDirectCall) {
		if (logger == null) {
			initLogger();
			if (!isSecondTimeCalled) {
				doError(log, true, isDirectCall);
			}
		} else {
			try {
				logger.error(withThread(log, isSecondTimeCalled, isDirectCall));
			} catch (AppenderLoggingException e) {
				logger = null;
				initLogger();
				if (!isSecondTimeCalled) {
					doError(log, true, isDirectCall);
				}
			}
		}
	}

	/**
	 * 如果isSecondTimeCalled为false，表示是首次调用，再次尝试以便发现新的配置文件。 如果isSecondTimeCalled为true，表示是第二次调用，不再尝试。
	 * 
	 * @param log
	 * @param isSecondTimeCalled
	 */
	private void doFatal(String log, boolean isSecondTimeCalled,
			boolean isDirectCall) {
		if (logger == null) {
			initLogger();
			if (!isSecondTimeCalled) {
				doFatal(log, true, isDirectCall);
			}
		} else {
			try {
				logger.fatal(withThread(log, isSecondTimeCalled, isDirectCall));
			} catch (AppenderLoggingException e) {
				logger = null;
				initLogger();
				if (!isSecondTimeCalled) {
					doFatal(log, true, isDirectCall);
				}
			}
		}
	}

	/**
	 * 如果isSecondTimeCalled为false，表示是首次调用，再次尝试以便发现新的配置文件。 如果isSecondTimeCalled为true，表示是第二次调用，不再尝试。
	 * 
	 * @param log
	 * @param isSecondTimeCalled
	 */
	private void doCatching(Throwable throwable, boolean isSecondTimeCalled,
			boolean isDirectCall) {
		if (logger == null) {
			initLogger();
			if (!isSecondTimeCalled) {
				doCatching(throwable, true, isDirectCall);
			}
		} else {
			try {
				logger.error(withThread("Catching", isSecondTimeCalled,
						isDirectCall), throwable);
			} catch (AppenderLoggingException e) {
				logger = null;
				initLogger();
				if (!isSecondTimeCalled) {
					doCatching(throwable, true, isDirectCall);
				}
			}
		}
	}

	public static void trace(String log) {
		InnerInstance.instance.doTrace(log, false, true);
	}

	public static void trace(String log, boolean isDirectCall) {
		InnerInstance.instance.doTrace(log, false, isDirectCall);
	}

	public static void debug(String log) {
		InnerInstance.instance.doDebug(log, false, true);
	}

	public static void debug(String log, boolean isDirectCall) {
		InnerInstance.instance.doDebug(log, false, isDirectCall);
	}

	public static void info(String log) {
		InnerInstance.instance.doInfo(log, false, true);
	}

	public static void info(String log, boolean isDirectCall) {
		InnerInstance.instance.doInfo(log, false, isDirectCall);
	}

	public static void warn(String log) {
		InnerInstance.instance.doWarn(log, false, true);
	}

	public static void warn(String log, boolean isDirectCall) {
		InnerInstance.instance.doWarn(log, false, isDirectCall);
	}

	public static void error(String log) {
		InnerInstance.instance.doError(log, false, true);
	}

	public static void error(String log, boolean isDirectCall) {
		InnerInstance.instance.doError(log, false, isDirectCall);
	}

	public static void fatal(String log) {
		InnerInstance.instance.doFatal(log, false, true);
	}

	public static void fatal(String log, boolean isDirectCall) {
		InnerInstance.instance.doFatal(log, false, isDirectCall);
	}

	public static void catching(Throwable throwable) {
		InnerInstance.instance.doCatching(throwable, false, true);
	}

	public static void catching(Throwable throwable, boolean isDirectCall) {
		InnerInstance.instance.doCatching(throwable, false, isDirectCall);
	}
}
